// 数组长度做计数

{
  // TypeScript 类型系统没有加减乘除运算符，怎么做数值运算呢？
  // 不知道大家有没有注意到数组类型取 length 就是数值。
  // 比如：

  type num1 = [unknown]['length']; // type num1 = 1

  type num2 = [unknown, unknown]['length']; // type num2 = 2

  /*
    而数组类型我们是能构造出来的，那么通过构造不同长度的数组然后取 length，不就是数值的运算么？

    TypeScript 类型系统中没有加减乘除运算符，但是可以通过构造不同的数组然后取 length 的方式来完成数值计算，把数值的加减乘除转化为对数组的提取和构造。
  */
}

// 数组长度实现加减乘除

// 构造一个指定长度的数组
type BuildArray<
  Length extends number,
  Ele = unknown,
  Arr extends unknown[] = [],
> = Arr['length'] extends Length ? Arr : BuildArray<Length, Ele, [...Arr, Ele]>;

type len4 = BuildArray<4>;

{
  // 加法

  // type resEqual50 = 50
  type resEqual50 = [...BuildArray<20>, ...BuildArray<30>]['length'];
}

{
  type Subtract<Num1 extends number, Num2 extends number> = BuildArray<Num1> extends [
    ...arr1: BuildArray<Num2>,
    ...arr2: infer Rest
  ]
    ? Rest['length']
    : never;

  type res = Subtract<5, 3>; // type res = 2

  /*
    减法是从数值中去掉一部分，很容易想到可以通过数组类型的提取来做。
    比如 3 是 [unknown, unknown, unknown] 的数组类型，提取出 2 个元素之后，剩下的数组再取 length 就是 1。
    类型参数 Num1、Num2 分别是被减数和减数，通过 extends 约束为 number。
    构造 Num1 长度的数组，通过模式匹配提取出 Num2 长度个元素，剩下的放到 infer 声明的局部变量 Rest 里。
    取 Rest 的长度返回，就是减法的结果。
  */
}

{
  // 乘法

  /*
    1 乘以 5 就相当于 1 + 1 + 1 + 1 + 1，也就是说乘法就是多个加法结果的累加。
    那么我们在加法的基础上，多加一个参数来传递中间结果的数组，算完之后再取一次 length 就能实现乘法
  */

  type BuildArray<
    Length extends number,
    Ele = unknown,
    Arr extends unknown[] = [],
  > = Arr['length'] extends Length ? Arr : BuildArray<Length, Ele, [...Arr, Ele]>;

  type Subtract<Num1 extends number, Num2 extends number> = BuildArray<Num1> extends [
    ...arr1: BuildArray<Num2>,
    ...arr2: infer Rest
  ]
    ? Rest['length']
    : never;

  type Mutiply<
    Num1 extends number,
    Num2 extends number,
    ResultArr extends unknown[] = [],
  > = Num2 extends 0
    ? ResultArr['length']
    : Mutiply<Num1, Subtract<Num2, 1>, [...BuildArray<Num1>, ...ResultArr]>;

  type res = Mutiply<3, 5>;

  /*
    类型参数 Num1 和 Num2 分别是被加数和加数。
    因为乘法是多个加法结果的累加，我们加了一个类型参数 ResultArr 来保存中间结果，默认值是 []，相当于从 0 开始加。
    每加一次就把 Num2 减一，直到 Num2 为 0，就代表加完了。
    加的过程就是往 ResultArr 数组中放 Num1 个元素。
    这样递归的进行累加，也就是递归的往 ResultArr 中放元素。
    最后取 ResultArr 的 length 就是乘法的结果。
    就这样，我们通过递归的累加实现了乘法。
  */
}

{
  // 乘法是递归的累加，那除法不就是递归的累减么？

  type Subtract<Num1 extends number, Num2 extends number> = BuildArray<Num1> extends [
    ...arr1: BuildArray<Num2>,
    ...arr2: infer Rest
  ]
    ? Rest['length']
    : never;

  type Divide<
    Num1 extends number,
    Num2 extends number,
    CountArr extends unknown[] = [],
  > = Num1 extends 0
    ? CountArr['length']
    : Divide<Subtract<Num1, Num2>, Num2, [unknown, ...CountArr]>;

  type res = Divide<6, 3>;

  /*
    我们有 9 个苹果，分给美羊羊 3 个，分给懒羊羊 3 个，分给沸羊羊 3 个，最后剩下 0 个。所以 9 / 3 = 3。
    所以，除法的实现就是被减数不断减去减数，直到减为 0，记录减了几次就是结果。
    如果 Num1 减到了 0 ，那么这时候减了几次就是除法结果，也就是 CountArr['length']。
    否则继续递归的减，让 Num1 减去 Num2，并且 CountArr 多加一个元素代表又减了一次。
    这样就实现了除法：
  */
}

{
  /*
    数组长度实现计数 StrLen

    数组长度可以取 length 来得到，但是字符串类型不能取 length，所以我们来实现一个求字符串长度的高级类型。
    
    字符串长度不确定，明显要用递归。每次取一个并计数，直到取完，就是字符串长度。
  */

  type StrLen<
    Str extends string,
    CountArr extends unknown[] = [],
  > = Str extends `${string}${infer Rest}`
    ? StrLen<Rest, [...CountArr, unknown]>
    : CountArr['length'];

  // xxx extends xxx 时 ts是会多次判断执行的
  // 每次判断一次，往CountArr这个类型里塞一个值 最后统计其length 就是迭代了几次
  // 也就是最终这个字符串的长度

  /*
    类型参数 Str 是待处理的字符串。类型参数 CountArr 是做计数的数组，默认值 [] 代表从 0 开始。
    每次通过模式匹配提取去掉一个字符之后的剩余字符串，并且往计数数组里多放入一个元素。递归进行取字符和计数。
    如果模式匹配不满足，代表计数结束，返回计数数组的长度 CountArr['length']。
    这样就能求出字符串长度：
  */
}

{
  // GreaterThan
  // 能够做计数了，那也就能做两个数值的比较。
  // 我们往一个数组类型中不断放入元素取长度，如果先到了 A，那就是 B 大，否则是 A 大：

  type GreaterThan<
    Num1 extends number,
    Num2 extends number,
    CountArr extends unknown[] = [],
  > = Num1 extends Num2
    ? false
    : CountArr['length'] extends Num2
    ? true
    : CountArr['length'] extends Num1
    ? false
    : GreaterThan<Num1, Num2, [...CountArr, unknown]>;

  type res = GreaterThan<3, 4>; // false
  type res2 = GreaterThan<4, 3>; // true
  type res3 = GreaterThan<4, 4>; // false

  // A > B ?

  /*
    类型参数 Num1 和 Num2 是待比较的两个数。
    类型参数 CountArr 是计数用的，会不断累加，默认值是 [] 代表从 0 开始。
    如果 Num1 extends Num2 成立，代表相等，直接返回 false。
    否则判断计数数组的长度，如果先到了 Num2，那么就是 Num1 大，返回 true。
    反之，如果先到了 Num1，那么就是 Num2 大，返回 false。
    如果都没到就往计数数组 CountArr 中放入一个元素，继续递归。
    这样就实现了数值比较。
  */
}

// TypeScript 类型系统没有加减乘除运算符，所以我们通过数组类型的构造和提取，然后取长度的方式来实现数值运算。
